agora inbox for [email protected]
help / color / mirror / Atom feed[PATCH v32 01/11] Add a syntax to create Incrementally Maintainable Materialized Views
671+ messages / 2 participants
[nested] [flat]
* [PATCH v32 01/11] Add a syntax to create Incrementally Maintainable Materialized Views
@ 2019-12-20 01:05 Yugo Nagata <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Yugo Nagata @ 2019-12-20 01:05 UTC (permalink / raw)
Allow to create Incrementally Maintainable Materialized View (IMMV)
by using INCREMENTAL option in CREATE MATERIALIZED VIEW command
as follow:
CREATE [INCREMANTAL] MATERIALIZED VIEW xxxxx AS SELECT ....;
---
src/backend/parser/gram.y | 32 +++++++++++++++++++++-----------
src/include/nodes/primnodes.h | 1 +
src/include/parser/kwlist.h | 1 +
3 files changed, 23 insertions(+), 11 deletions(-)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 682748eb4b..91df005a19 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -468,6 +468,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <range> OptTempTableName
%type <into> into_clause create_as_target create_mv_target
+%type <boolean> incremental
%type <defelt> createfunc_opt_item common_func_opt_item dostmt_opt_item
%type <fun_param> func_arg func_arg_with_default table_func_column aggr_arg
@@ -732,7 +733,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
HANDLER HAVING HEADER_P HOLD HOUR_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE
- INCLUDING INCREMENT INDENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+ INCLUDING INCREMENT INCREMENTAL INDENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -4734,32 +4735,34 @@ opt_with_data:
*****************************************************************************/
CreateMatViewStmt:
- CREATE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
+ CREATE OptNoLog incremental MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data
{
CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
- ctas->query = $7;
- ctas->into = $5;
+ ctas->query = $8;
+ ctas->into = $6;
ctas->objtype = OBJECT_MATVIEW;
ctas->is_select_into = false;
ctas->if_not_exists = false;
/* cram additional flags into the IntoClause */
- $5->rel->relpersistence = $2;
- $5->skipData = !($8);
+ $6->rel->relpersistence = $2;
+ $6->skipData = !($9);
+ $6->ivm = $3;
$$ = (Node *) ctas;
}
- | CREATE OptNoLog MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
+ | CREATE OptNoLog incremental MATERIALIZED VIEW IF_P NOT EXISTS create_mv_target AS SelectStmt opt_with_data
{
CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
- ctas->query = $10;
- ctas->into = $8;
+ ctas->query = $11;
+ ctas->into = $9;
ctas->objtype = OBJECT_MATVIEW;
ctas->is_select_into = false;
ctas->if_not_exists = true;
/* cram additional flags into the IntoClause */
- $8->rel->relpersistence = $2;
- $8->skipData = !($11);
+ $9->rel->relpersistence = $2;
+ $9->skipData = !($12);
+ $9->ivm = $3;
$$ = (Node *) ctas;
}
;
@@ -4776,9 +4779,14 @@ create_mv_target:
$$->tableSpaceName = $5;
$$->viewQuery = NULL; /* filled at analysis time */
$$->skipData = false; /* might get changed later */
+ $$->ivm = false;
}
;
+incremental: INCREMENTAL { $$ = true; }
+ | /*EMPTY*/ { $$ = false; }
+ ;
+
OptNoLog: UNLOGGED { $$ = RELPERSISTENCE_UNLOGGED; }
| /*EMPTY*/ { $$ = RELPERSISTENCE_PERMANENT; }
;
@@ -17454,6 +17462,7 @@ unreserved_keyword:
| INCLUDE
| INCLUDING
| INCREMENT
+ | INCREMENTAL
| INDENT
| INDEX
| INDEXES
@@ -18037,6 +18046,7 @@ bare_label_keyword:
| INCLUDE
| INCLUDING
| INCREMENT
+ | INCREMENTAL
| INDENT
| INDEX
| INDEXES
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index aa727e722c..9d0ba29a2a 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -154,6 +154,7 @@ typedef struct IntoClause
/* materialized view's SELECT query */
Node *viewQuery pg_node_attr(query_jumble_ignore);
bool skipData; /* true for WITH NO DATA */
+ bool ivm; /* true for WITH IVM */
} IntoClause;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 6c959e85d5..94c4a3de1a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -210,6 +210,7 @@ PG_KEYWORD("in", IN_P, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("incremental", INCREMENTAL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("indent", INDENT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD, BARE_LABEL)
--
2.25.1
--Multipart=_Sun__31_Mar_2024_22_59_31_+0900_msknEviJj08_wgqO
Content-Type: text/x-diff;
name="v32-0002-Add-relisivm-column-to-pg_class-system-catalog.patch"
Content-Disposition: attachment;
filename="v32-0002-Add-relisivm-column-to-pg_class-system-catalog.patch"
Content-Transfer-Encoding: 7bit
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-05 18:58 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-05 18:58 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--k4rn5ob3nx2ufkij--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ae0b011f6c1..ee763c995f0 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v42-0006-Fix-a-few-problems-in-index-build-progress-reporting.patch
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0009-Fix-a-few-problems-in-index-build-progress-repor.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index df12117236a..f6bfb282489 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -708,6 +713,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -743,6 +749,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -974,8 +982,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1023,10 +1035,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1038,6 +1055,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2430,56 +2449,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v47-0006-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--cldir6umqisr673d
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v45-0005-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 618b831e8eb..065bb233fe2 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -50,6 +50,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -702,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -737,6 +743,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -966,8 +974,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1015,10 +1027,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1030,6 +1047,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2422,56 +2441,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--nzintiedl6o4kcyp
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v43-0005-Fix-crash-caused-by-involuntary-decoding-of-unre.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0ba7c0df1fc..fa95deb46ea 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -703,6 +708,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -738,6 +744,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -967,8 +975,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1016,10 +1028,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1031,6 +1048,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2423,56 +2442,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--mdsv5bdgfjjj6d77
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v46-0004-rename-cluster.c-h-to-repack.c-h.patch"
^ permalink raw reply [nested|flat] 671+ messages in thread
* [PATCH 5/5] Use BulkInsertState when copying data to the new heap.
@ 2026-03-11 14:20 Antonin Houska <[email protected]>
0 siblings, 0 replies; 671+ messages in thread
From: Antonin Houska @ 2026-03-11 14:20 UTC (permalink / raw)
It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.
This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
1 file changed, 72 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
double *tups_recently_dead)
{
RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (!concurrent)
rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
*xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
/* Write out any remaining tuples, and fsync if needed */
if (rwstate)
end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
* SET WITHOUT OIDS.
*
* So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
*/
static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- if (rwstate)
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
- else
- {
- /*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * The following is like simple_heap_insert() except that we pass the
- * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
- * the relation files, it drops this relation, so no logical
- * replication subscription should need the data.
- */
- heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
- HEAP_INSERT_NO_LOGICAL, NULL);
- }
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
--
2.47.3
--=-=-=--
^ permalink raw reply [nested|flat] 671+ messages in thread
end of thread, other threads:[~2026-03-11 14:20 UTC | newest]
Thread overview: 671+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2019-12-20 01:05 [PATCH v32 01/11] Add a syntax to create Incrementally Maintainable Materialized Views Yugo Nagata <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-05 18:58 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-09 10:35 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v44 08/10] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v47 5/9] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v45 4/8] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v43 4/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH v46 3/7] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-03-11 14:20 [PATCH 5/5] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox