agora inbox for pgsql-hackers@postgresql.org
help / color / mirror / Atom feed[PATCH v47 4/9] repack code cleanups
325+ messages / 1 participants
[nested] [flat]
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)
---
src/backend/commands/cluster.c | 163 +++++++++++----------------------
1 file changed, 51 insertions(+), 112 deletions(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
TupleTableSlot *spilled_tuple;
TupleTableSlot *old_update_tuple;
TupleTableSlot *ondisk_tuple;
- MemoryContext apply_cxt;
bool have_old_tuple = false;
-
- /*
- * Use a separate memory context for the tuples and any memory needed to
- * process them.
- *
- * XXX would this be better with GenerationContextCreate?
- */
- apply_cxt = AllocSetContextCreate(TopTransactionContext,
- "REPACK - apply",
- ALLOCSET_DEFAULT_SIZES);
+ MemoryContext oldcxt;
spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
&TTSOpsVirtual);
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
while (true)
{
size_t nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
else
elog(ERROR, "unrecognized kind of change: %d", kind);
- MemoryContextReset(apply_cxt);
+ ResetPerTupleExprContext(chgcxt->cc_estate);
}
/* Cleanup. */
ExecDropSingleTupleTableSlot(spilled_tuple);
ExecDropSingleTupleTableSlot(ondisk_tuple);
ExecDropSingleTupleTableSlot(old_update_tuple);
- MemoryContextDelete(apply_cxt);
+
+ MemoryContextSwitchTo(oldcxt);
}
/*
@@ -2588,27 +2581,17 @@ static void
apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
ChangeContext *chgcxt)
{
- List *recheck;
-
/* Put the tuple in the table, but make sure it won't be decoded */
table_tuple_insert(rel, slot, GetCurrentCommandId(true),
HEAP_INSERT_NO_LOGICAL, NULL);
- /*
- * Update indexes with this new tuple. Because we're merely replaying an
- * action that already happened, we have no use for the recheck list of
- * indexes returned, so just free it. XXX or maybe just leave it?
- */
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- 0,
- slot,
- NIL, NULL);
- list_free(recheck);
-
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
- ResetPerTupleExprContext(chgcxt->cc_estate);
}
/*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
TM_FailureData tmfd;
TU_UpdateIndexes update_indexes;
TM_Result res;
- List *recheck;
/*
- * Carry out the update, avoiding logical decoding info.
+ * Carry out the update, skipping logical decoding for it.
*/
res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
if (update_indexes == TU_Summarizing)
flags |= EIIT_ONLY_SUMMARIZING;
- recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
- chgcxt->cc_estate,
- flags,
- spilled_tuple,
- NIL, NULL);
- list_free(recheck);
- ResetPerTupleExprContext(chgcxt->cc_estate);
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
}
pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
TM_FailureData tmfd;
/*
- * Delete tuple from the new heap.
- *
- * Do it like in simple_heap_delete(), except for 'wal_logical' (and
- * except for 'wait').
+ * Delete tuple from the new heap, skipping logical decoding for it.
*/
res = table_tuple_delete(rel, &(slot->tts_tid),
GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
char relpersistence;
bool is_system_catalog;
Oid ident_idx_new;
- XLogRecPtr wal_insert_ptr,
- end_of_wal;
- char dummy_rec_data = '\0';
- Relation *ind_refs,
- *ind_refs_p;
- int nind;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
ChangeContext chgcxt;
/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
*/
ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
- /* Find "identity index" on the new relation. */
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
ident_idx_new = InvalidOid;
- forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ foreach_oid(ind_old, ind_oids_old)
{
- Oid ind_old = lfirst_oid(lc);
- Oid ind_new = lfirst_oid(lc2);
-
if (identIdx == ind_old)
{
- ident_idx_new = ind_new;
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
break;
}
}
-
- /* Should not happen, given our lock on the old relation. */
if (!OidIsValid(ident_idx_new))
- ereport(ERROR,
- errmsg("identity index missing on the new relation"));
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
/* Gather information to apply concurrent changes. */
initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* incomplete page; see GetInsertRecPtr), to minimize the amount of data
* we need to flush while holding exclusive lock on the source table.
*/
- wal_insert_ptr = GetInsertRecPtr();
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
/*
* Lock all indexes now, not only the clustering one: all indexes need to
* have their files swapped. While doing that, store their relation
- * references in an array, to handle predicate locks below.
+ * references in a zero-terminated array, to handle predicate locks below.
*/
- ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
- nind = 0;
+ indexrels = NIL;
foreach_oid(ind_oid, ind_oids_old)
{
Relation index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
index = index_open(ind_oid, AccessExclusiveLock);
/*
- * TODO 1) Do we need to check if ALTER INDEX was executed since the
- * new index was created in build_new_indexes()? 2) Specifically for
- * the clustering index, should check_index_is_clusterable() be called
- * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
- * table probably blocks all commands that affect the result of
- * check_index_is_clusterable().)
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
*/
- *ind_refs_p = index;
- ind_refs_p++;
- nind++;
+ indexrels = lappend(indexrels, index);
}
/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
* Tuples and pages of the old heap will be gone, but the heap will stay.
*/
TransferPredicateLocksToHeapRelation(OldHeap);
- /* The same for indexes. */
- for (int i = 0; i < nind; i++)
+ foreach_ptr(RelationData, index, indexrels)
{
- Relation index = ind_refs[i];
-
TransferPredicateLocksToHeapRelation(index);
-
- /*
- * References to indexes on the old relation are not needed anymore,
- * however locks stay till the end of the transaction.
- */
index_close(index, NoLock);
}
- pfree(ind_refs);
+ list_free(indexrels);
/*
- * Flush anything we see in WAL, to make sure that all changes committed
- * while we were waiting for the exclusive lock are available for
- * decoding. This should not be necessary if all backends had
- * synchronous_commit set, but we can't rely on this setting.
- *
- * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
- * position, and GetLastImportantRecPtr() points at the start of the last
- * record rather than at the end. Thus the simplest way to determine the
- * insert position is to insert a dummy record and use its LSN.
- *
- * XXX Consider using GetLastImportantRecPtr() and adding the size of the
- * last record (plus the total size of all the page headers the record
- * spans)?
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
*/
- XLogBeginInsert();
- XLogRegisterData(&dummy_rec_data, 1);
- wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
- XLogFlush(wal_insert_ptr);
+ XLogFlush(GetXLogInsertRecPtr());
end_of_wal = GetFlushRecPtr(NULL);
/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
{
Oid ind_old = lfirst_oid(lc);
Oid ind_new = lfirst_oid(lc2);
- Oid mapped_tables[4];
-
- /* Zero out possible results from swapped_relation_files */
- memset(mapped_tables, 0, sizeof(mapped_tables));
+ Oid mapped_tables[4] = {0};
swap_relation_files(ind_old, ind_new,
(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
mapped_tables);
#ifdef USE_ASSERT_CHECKING
-
/*
* Concurrent processing is not supported for system relations, so
* there should be no mapped tables.
*/
for (int i = 0; i < 4; i++)
- Assert(mapped_tables[i] == 0);
+ Assert(!OidIsValid(mapped_tables[i]));
#endif
}
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_REBUILD_INDEX);
- foreach_oid(ind_oid, OldIndexes)
+ foreach_oid(oldindex, OldIndexes)
{
- Oid ind_oid_new;
+ Oid newindex;
char *newName;
Relation ind;
- ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
- newName = ChooseRelationName(get_rel_name(ind_oid),
+ newName = ChooseRelationName(get_rel_name(oldindex),
NULL,
"repacknew",
get_rel_namespace(ind->rd_index->indrelid),
false);
- ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
- ind->rd_rel->reltablespace, newName);
- result = lappend_oid(result, ind_oid_new);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ result = lappend_oid(result, newindex);
index_close(ind, NoLock);
}
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v52 02/10] Rename cluster.c/h -> repack.c/h
@ 2026-03-31 16:55 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 325+ messages in thread
From: Álvaro Herrera @ 2026-03-31 16:55 UTC (permalink / raw)
---
src/backend/commands/Makefile | 2 +-
src/backend/commands/matview.c | 2 +-
src/backend/commands/meson.build | 2 +-
src/backend/commands/{cluster.c => repack.c} | 6 +++---
src/backend/commands/tablecmds.c | 2 +-
src/backend/commands/vacuum.c | 6 +++---
src/backend/storage/ipc/procsignal.c | 1 +
src/backend/tcop/postgres.c | 1 +
src/backend/tcop/utility.c | 2 +-
src/include/commands/{cluster.h => repack.h} | 12 ++++++------
10 files changed, 19 insertions(+), 17 deletions(-)
rename src/backend/commands/{cluster.c => repack.c} (99%)
rename src/include/commands/{cluster.h => repack.h} (90%)
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index c10fdba2bbb..fe1bba3a9b9 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -18,7 +18,6 @@ OBJS = \
amcmds.o \
analyze.o \
async.o \
- cluster.o \
collationcmds.o \
comment.o \
constraint.o \
@@ -51,6 +50,7 @@ OBJS = \
proclang.o \
propgraphcmds.o \
publicationcmds.o \
+ repack.o \
schemacmds.o \
seclabel.o \
sequence.o \
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index d3be8939011..5db4fe75dce 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -24,8 +24,8 @@
#include "catalog/namespace.h"
#include "catalog/pg_am.h"
#include "catalog/pg_opclass.h"
-#include "commands/cluster.h"
#include "commands/matview.h"
+#include "commands/repack.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "executor/executor.h"
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index 90c7e37a429..f624aae74af 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -6,7 +6,6 @@ backend_sources += files(
'amcmds.c',
'analyze.c',
'async.c',
- 'cluster.c',
'collationcmds.c',
'comment.c',
'constraint.c',
@@ -39,6 +38,7 @@ backend_sources += files(
'proclang.c',
'propgraphcmds.c',
'publicationcmds.c',
+ 'repack.c',
'schemacmds.c',
'seclabel.c',
'sequence.c',
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/repack.c
similarity index 99%
rename from src/backend/commands/cluster.c
rename to src/backend/commands/repack.c
index f241e18b153..20f0a572236 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/repack.c
@@ -1,6 +1,6 @@
/*-------------------------------------------------------------------------
*
- * cluster.c
+ * repack.c
* REPACK a table; formerly known as CLUSTER. VACUUM FULL also uses
* parts of this code.
*
@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
- * src/backend/commands/cluster.c
+ * src/backend/commands/repack.c
*
*-------------------------------------------------------------------------
*/
@@ -33,9 +33,9 @@
#include "catalog/pg_am.h"
#include "catalog/pg_inherits.h"
#include "catalog/toasting.h"
-#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0ce2e81f9c2..e2882a50b3b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -57,10 +57,10 @@
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
#include "catalog/toasting.h"
-#include "commands/cluster.h"
#include "commands/comment.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
+#include "commands/repack.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 0ed363d1c85..b179b62b5c8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -9,7 +9,7 @@
*
* VACUUM for heap AM is implemented in vacuumlazy.c, parallel vacuum in
* vacuumparallel.c, ANALYZE in analyze.c, and VACUUM FULL is a variant of
- * CLUSTER, handled in cluster.c.
+ * REPACK, handled in repack.c.
*
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
@@ -38,9 +38,9 @@
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "commands/async.h"
-#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -2293,7 +2293,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
if ((params.options & VACOPT_VERBOSE) != 0)
cluster_params.options |= CLUOPT_VERBOSE;
- /* VACUUM FULL is a variant of REPACK; see cluster.c */
+ /* VACUUM FULL is a variant of REPACK; see repack.c */
cluster_rel(REPACK_COMMAND_VACUUMFULL, rel, InvalidOid,
&cluster_params);
/* cluster_rel closes the relation, but keeps lock */
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 7e017c8d53b..7cef6e43661 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -19,6 +19,7 @@
#include "access/parallel.h"
#include "commands/async.h"
+#include "commands/repack.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "port/pg_bitutils.h"
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 10be60011ad..9fbaa5c00f0 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -39,6 +39,7 @@
#include "commands/event_trigger.h"
#include "commands/explain_state.h"
#include "commands/prepare.h"
+#include "commands/repack.h"
#include "common/pg_prng.h"
#include "jit/jit.h"
#include "libpq/libpq.h"
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 2b609bfc824..5f8c766c4be 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -26,7 +26,6 @@
#include "catalog/toasting.h"
#include "commands/alter.h"
#include "commands/async.h"
-#include "commands/cluster.h"
#include "commands/collationcmds.h"
#include "commands/comment.h"
#include "commands/conversioncmds.h"
@@ -46,6 +45,7 @@
#include "commands/proclang.h"
#include "commands/propgraphcmds.h"
#include "commands/publicationcmds.h"
+#include "commands/repack.h"
#include "commands/schemacmds.h"
#include "commands/seclabel.h"
#include "commands/sequence.h"
diff --git a/src/include/commands/cluster.h b/src/include/commands/repack.h
similarity index 90%
rename from src/include/commands/cluster.h
rename to src/include/commands/repack.h
index d6b62c747e8..85061158b0c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/repack.h
@@ -1,17 +1,17 @@
/*-------------------------------------------------------------------------
*
- * cluster.h
- * header file for postgres cluster command stuff
+ * repack.h
+ * header file for the REPACK command
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994-5, Regents of the University of California
*
- * src/include/commands/cluster.h
+ * src/include/commands/repack.h
*
*-------------------------------------------------------------------------
*/
-#ifndef CLUSTER_H
-#define CLUSTER_H
+#ifndef REPACK_H
+#define REPACK_H
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
@@ -52,4 +52,4 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-#endif /* CLUSTER_H */
+#endif /* REPACK_H */
--
2.47.3
--gp2pyozrd5pweboh
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v52-0003-Add-CONCURRENTLY-option-to-REPACK-command.patch"
^ permalink raw reply [nested|flat] 325+ messages in thread
end of thread, other threads:[~2026-03-31 16:55 UTC | newest]
Thread overview: 325+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 16:55 [PATCH v52 02/10] Rename cluster.c/h -> repack.c/h Álvaro Herrera <alvherre@kurilemu.de>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox